/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the robot4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */

package com.hitqz.robot.daemon.quartz.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.cache.Cache;
import com.google.common.collect.Lists;
import com.hitqz.robot.admin.api.entity.SysDictItem;
import com.hitqz.robot.api.business.dto.RobotClientSummaryDto;
import com.hitqz.robot.api.business.dto.SubscriptTopicDto;
import com.hitqz.robot.api.business.entity.*;
import com.hitqz.robot.api.dispatch.protocol.dto.FormConfigModalDto;
import com.hitqz.robot.api.dispatch.protocol.dto.FormFieldConfigModalDto;
import com.hitqz.robot.api.dispatch.protocol.dto.RobotNextStepDto;
import com.hitqz.robot.biz.service.*;
import com.hitqz.robot.common.core.util.R;
import com.hitqz.robot.common.log.annotation.SysLog;
//import com.hitqz.robot.daemon.quartz.dao.AgvDao;
import com.hitqz.robot.daemon.quartz.model.*;
import com.hitqz.robot.daemon.quartz.service.AgvStoreService;
import com.hitqz.robot.daemon.quartz.service.SysJobService;
import com.hitqz.robot.daemon.quartz.util.IdGenerator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author guoliang
 * @date 2024-3-26 11:19:18
 * <p>
 * 定时任务管理
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("")
@Tag(name = "AGV呼叫系统--哈尔滨车辆段")
@Slf4j
public class AgvController {
    private final TicketsService ticketsService;
    private final RobotJobService robotJobService;

    private final SysJobService sysJobService;

    private final RestTemplate restTemplate;

    private final RobotService robotService;

    private final GoodsBuffersService goodsBuffersService;

    private final GoodsGroupService goodsGroupService;

    private final JobCachesService jobCachesService;

    private final AgvStoreService agvStoreService;

    @Value("${robot.debug:false}")
    private boolean robotDebug;

    // 等待通知接口为 /agv/arriveResponse
    // 任务流程： 轮对。 轮对左/右(911008/911007)  轮对校验取前置点(911016) 等待通知 然后前往(911009) 放下后到达 轮对校验取前置点(911016) 通知 缓存完成
    // 911008 南面 911007 北面

    /**
     * 第一列 优先报废
     * 其他 2-8
     * 返修 8->2 //
     * 4 前面4个 返。后4个 旧
     * 旧 2-> 8  // 不分zx
     * src
     * des
     * 911017 缓存架 911006返修架 911015 返修架 911006
     * 报废-缓存: 前往报废架前置点(911004)，等待通知下一步报废架(911015)，前往报废架前置点(911004),通知取完成，到达缓存架(911017), 通知放完成（带上位置）
     * 报废(缓存架 zx)-下线: 前往报废架前置点(911014)，等待通知下一步报废架(911005)，前往报废架前置点(911004),通知取完成，到达报废架下线(911020),等待操作完成离开
     * 缓存(报废)-下线
     * 返修-缓存: 前往报废架前置点(911015)，等待通知下一步返修架(911006)，前往返修架前置点(911006),通知取完成，到达缓存架(911017), 通知放完成（带上位置）
     * 。 返修-上线: 前往报废架前置点(911015)，等待通知下一步返修架(911006)，前往返修架前置点(911006),通知取完成，上线前置点(911013), 等待通知，前往(911012), 上线前置点(911013),通知放完成（带上位置）
     * 。 新轴-上线: 前往新轴前置点(91118)，等待通知下一步新轴(91101)，前往新轴前置点(91118),通知取完成，上线前置点(911013), 等待通知，前往(911012), 上线前置点(911013),通知放完成
     * 。 旧轴-上线: 前往旧轴前置点(91102)，等待通知下一步旧轴(91102)，前往新轴前置点(91119),通知取完成，上线前置点(911013), 等待通知，前往(911012), 上线前置点(911013),通知放完成
     * 缓存架(新/旧/返修 zx查询)-上线:前往报废架前置点(911015)，等待通知下一步返修架(911006)，前往报废架前置点(911006),通知取完成，上线前置点(911013), 等待通知，前往(911012), 上线前置点(911013),通知放完成（带上位置）
     * 缓存架- 饭修(8排)-> 缓存架(新/旧/返修 zx查询) 新增-》接口
     * 1-2-3-4 路径资源 4
     * 0-1-2 2 0
     *
     * @param agvCallRequestParams
     * @return
     * @throws SchedulerException
     */
    @PostMapping(value = "/agv/call")
    @Operation(summary = "AGV请求任务")
    //  车轴 1.185
    // 0.166
    @SysLog("哈辆-任务呼叫")
    public AgvCallResult call(@RequestBody AgvCallRequestParams agvCallRequestParams) throws SchedulerException {
        try {
            log.info("自动任务Calling agv: {}", JSONObject.toJSONString(agvCallRequestParams));
            String taskName = createTaskName(agvCallRequestParams.getSrc(), agvCallRequestParams.getDes());
            String src = agvCallRequestParams.getSrc();
            String dest = agvCallRequestParams.getDes();
            Integer num = agvCallRequestParams.getSrcnum();
            Integer srcPlace = agvCallRequestParams.getSrcplace();
            List<AgvCallDataDto> data = agvCallRequestParams.getData();
            List<AgvCallDataDto> newData = Lists.newArrayList();
            log.info("接受到任务: {}---{}", src, dest);
            RobotJobEntity robotJob = new RobotJobEntity();
            robotJob.setCron("0 * * * * ?");
            robotJob.setName(taskName + ":时间-" + DateUtil.format(new Date(), "yyyyMMddHH:mm:ss"));
            if (agvCallRequestParams.getData() != null) {
                robotJob.setRemark(JSONObject.toJSONString(agvCallRequestParams.getData()));
            }
            robotJob.setGroupId(6);
            String content = "{\"robotSn\":\"sr_002\",\"id\":null,\"formTitle\":\"运输工单上下文参数配置\",\"formName\":\"tickets_params_agv\",\"fields\":[{\"id\":45,\"name\":\"targetPosCode\",\"label\":\"目标点位编码\",\"type\":\"radio\",\"defaultValue\":null,\"currentValue\":\"LM34\",\"required\":true,\"min\":null,\"max\":null,\"pattern\":null,\"options\":[\"{\\\"label\\\":\\\"LM34\\\",\\\"value\\\":\\\"LM34\\\"}\"]},{\"id\":46,\"name\":\"sourcePosCode\",\"label\":\"起始点位编码\",\"type\":\"radio\",\"defaultValue\":null,\"currentValue\":\"LM23\",\"required\":true,\"min\":null,\"max\":null,\"pattern\":null,\"options\":[\"{\\\"label\\\":\\\"LM23\\\",\\\"value\\\":\\\"LM23\\\"}\"]}]}";
            content = content.replaceAll("LM34", dest).replaceAll("LM23", src);
            String zx = agvCallRequestParams.getZx();

            List<GoodsBuffersEntity> goodsBuffersEntities = new ArrayList<>();
            String operateType = "";
            if (src.equals("911017")) {
                operateType = "take";
                String sourcePos = agvCallRequestParams.getSourcePos();
                if (StringUtils.hasText(sourcePos)) {
                    GoodsBuffersEntity current = goodsBuffersService.getOne(Wrappers.<GoodsBuffersEntity>lambdaQuery()
                            .eq(GoodsBuffersEntity::getName, sourcePos)
                            .last("limit 1"));
                    if (current != null) {
                        goodsBuffersEntities = goodsBuffersService
                                .list(Wrappers.<GoodsBuffersEntity>lambdaQuery()
                                        .eq(GoodsBuffersEntity::getGroupId, current.getGroupId())
                                        .ge(GoodsBuffersEntity::getSn, current.getSn())
                                        .orderByAsc(GoodsBuffersEntity::getSn)
                                        .last("limit " + num)
                                );
                        //判断是否有车轴挡住无法取出
                        if (!goodsBuffersEntities.isEmpty()) {
                            GoodsBuffersEntity outside = goodsBuffersEntities.stream()
                                    .sorted(Comparator.comparing(GoodsBuffersEntity::getSn).reversed())
                                    .findFirst().orElse(null);
                            GoodsBuffersEntity exits = goodsBuffersService.getOne(Wrappers.<GoodsBuffersEntity>lambdaQuery()
                                    .eq(GoodsBuffersEntity::getGroupId, outside.getGroupId())
                                    .eq(GoodsBuffersEntity::getStoreStatus, 0)
                                    .gt(GoodsBuffersEntity::getSn, outside.getSn())
                                    .last("limit 1"));
                            if (exits != null) {
                                return AgvCallResult.builder().ret(false).message("有车轴挡住无法取出！").build();
                            }
                        }
                        goodsBuffersEntities.stream().filter(x -> x.getStoreStatus().intValue() != 1)
                                .forEach(y -> {
                                    y.setStoreStatus(3);
                                    y.setStoreTime(new Date());
                                    goodsBuffersService.updateById(y);
                                });
                    }
                } else {
                    goodsBuffersEntities = agvStoreService.findBestPlace(src, dest, num, zx, agvCallRequestParams.getSrcplace(),
                            Lists.newArrayList());
                }
                if (goodsBuffersEntities.isEmpty()) {
                    return AgvCallResult.builder().ret(false).message("缓存架找不到合适的缓存架").build();
                }
                content = content.replaceAll(src, goodsBuffersEntities.get(0).getBindPosName());
            } else if (dest.equals("911017")) {
                operateType = "put";
                goodsBuffersEntities = agvStoreService.findBestPlace(src, dest, num, zx, agvCallRequestParams.getSrcplace(), agvCallRequestParams.getData());
                if (goodsBuffersEntities.isEmpty()) {
                    return AgvCallResult.builder().ret(false).message("缓存架已满！").build();
                }
                content = content.replaceAll(dest, goodsBuffersEntities.get(0).getBindPosName());
            }

            for (GoodsBuffersEntity goodsBuffersEntity : goodsBuffersEntities) {
                String str = goodsBuffersEntity.getStoreData();
                if (StringUtils.hasText(str)) {
                    newData.add(JSONObject.parseObject(goodsBuffersEntity.getStoreData(), AgvCallDataDto.class));
                } else {
                    AgvCallDataDto dataDto = new AgvCallDataDto();
                    dataDto.setPj_id("");
                    dataDto.setPos(goodsBuffersEntity.getName());
                    dataDto.setZh("");
                    dataDto.setZx("");
                    newData.add(dataDto);
                }
            }
            if (!goodsBuffersEntities.isEmpty()) {
                num = goodsBuffersEntities.size();
            }
            if (newData.isEmpty()) {
                newData = agvCallRequestParams.getData();
            }
            FormConfigModalDto jsonArray = JSONObject.parseObject(content, FormConfigModalDto.class);
            List<FormFieldConfigModalDto> formFieldConfigModalDtos = jsonArray.getFields();
            FormFieldConfigModalDto dto = new FormFieldConfigModalDto();
            dto = new FormFieldConfigModalDto();
            dto.setLabel("operateType");
            dto.setName("operateType");
            dto.setCurrentValue("" + operateType);
            formFieldConfigModalDtos.add(dto);

            dto = new FormFieldConfigModalDto();
            dto.setLabel("srcplace");
            dto.setName("srcplace");
            dto.setCurrentValue("" + srcPlace);
            formFieldConfigModalDtos.add(dto);

            dto = new FormFieldConfigModalDto();
            dto.setLabel("srcnum");
            dto.setName("srcnum");
            dto.setCurrentValue("" + num);
            formFieldConfigModalDtos.add(dto);

            dto = new FormFieldConfigModalDto();
            dto.setLabel("pjData");
            dto.setName("pjData");
            dto.setCurrentValue(JSONObject.toJSONString(newData));
            formFieldConfigModalDtos.add(dto);

            dto = new FormFieldConfigModalDto();
            dto.setLabel("auto");
            dto.setName("auto");
            if (robotDebug) {
                dto.setCurrentValue("0");
            } else {
                dto.setCurrentValue("1");
            }
            formFieldConfigModalDtos.add(dto);

            dto = new FormFieldConfigModalDto();
            dto.setLabel("zx");
            dto.setName("zx");
            //dto.setCurrentValue("0");
            dto.setCurrentValue(zx);
            formFieldConfigModalDtos.add(dto);

            jsonArray.setFields(formFieldConfigModalDtos);
            robotJob.setContent(JSONObject.toJSONString(jsonArray));

            JobCachesEntity jobCachesEntity = new JobCachesEntity();
            // 两边都分配固定的分组id
            jobCachesEntity.setGroupId(6);
            jobCachesEntity.setTaskName(createTaskName(src, dest));
            jobCachesEntity.setJobType(jobCachesEntity.getTaskName());


            List<JobCachesEntity> list = jobCachesService.list(Wrappers.<JobCachesEntity>lambdaQuery()
                    .eq(JobCachesEntity::getGroupId, 6)
                    .orderByAsc(JobCachesEntity::getSn));
            int sn = list.isEmpty() ? 1 : list.get(list.size() - 1).getSn() + 1;
            // 重新排序,来源是加工线的返修和报废任务优先级高；
            if ("911006".equals(src) || "911005".equals(src)) {
                if (!list.isEmpty()) {
                    for (JobCachesEntity cachesEntity : list) {
                        cachesEntity.setSn(cachesEntity.getSn() + 1);
                    }
                    jobCachesService.updateBatchById(list);
                }
                sn = 1;
            }
            // 执行的顺序
            jobCachesEntity.setSn(sn);
            // 生成任务编号
            long snowflakeId = System.currentTimeMillis();
            jobCachesEntity.setTaskNo(String.valueOf(snowflakeId));
            jobCachesEntity.setTaskContent(robotJob.getContent());
            jobCachesService.save(jobCachesEntity);
            SpringUtil.getApplicationContext().publishEvent(new SubscriptTopicDto("jobCaches"));
            // 等待
            return AgvCallResult.builder()
                    .ret(true)
                    .message("")
                    .sbID("")
                    .taskCode(jobCachesEntity.getTaskNo())
                    .build();
        } catch (Exception e) {
            log.info("数据异常无法解析:" + e.getMessage());
        }
        return AgvCallResult.builder()
                .ret(false)
                .sbID("")
                .message("操作异常")
                .build();
    }


    public String createTaskName(String src, String des) {
        String taskName = "调试任务";
        if (src.equals("91101") && des.equals("911012")) {
            taskName = "新轴上线";
        } else if (src.equals("91102") && des.equals("911012")) {
            taskName = "旧轴上线";
        } else if (src.equals("91102") && des.equals("911017")) {
            taskName = "旧轴上缓存架";
        } else if ((src.equals("911005")) && des.equals("911020")) {
            taskName = "报废轴下线";
        } else if ((src.equals("911017")) && des.equals("911020")) {
            taskName = "报废轴缓存下线";
        } else if ((src.equals("911005")) && des.equals("911017")) {
            taskName = "报废轴上缓存架";
        } else if (src.equals("911017") && des.equals("911012")) {
            taskName = "缓存架上线";
        } else if (src.equals("911006") && des.equals("911012")) {
            taskName = "返修上线";
        } else if (src.equals("911006") && des.equals("911017")) {
            taskName = "返修上缓存架";
        } else if (src.equals("911008") && des.equals("911009")) {
            taskName = "南面(货车)";
        } else if (src.equals("911007") && des.equals("911009")) {
            taskName = "北面(客货车)";
        } else if (src.equals("911008") && des.equals("911007")) {
            taskName = "南面转北面";
        } else if (src.equals("911007") && des.equals("911008")) {
            taskName = "北面转南面";
        } else if (src.equals("911021") && des.equals("911012")) {
            taskName = "返修上线(玻璃房)";
        } else if (src.equals("911021") && des.equals("911017")) {
            taskName = "返修上缓存架(玻璃房)";
        } else if (src.equals("911022") && des.equals("911017")) {
            taskName = "报废上缓存(玻璃房)";
        } else if (src.equals("911022") && des.equals("911020")) {
            taskName = "报废下线(玻璃房)";
        }
        return taskName;
    }

    @PostMapping(value = "/agv/arriveResponse")
    @SysLog("哈辆-任务到达通知可以进行下一步操作")
    public AgvCallResult arriveResponse(@RequestBody AgvArriveResponseParams agvArriveResponseParams,
                                        HttpServletRequest request) {
        try {
            String ip = getClientIpAddress(request);
            log.info("arriveResponse: {}----{}", ip, JSONObject.toJSONString(agvArriveResponseParams));
        } catch (Exception e) {
            log.info("获取ip异常");
        }
        RobotNextStepDto robotNextStepDto = new RobotNextStepDto();
        robotNextStepDto.setTaskCode(agvArriveResponseParams.getTaskCode());
        robotNextStepDto.setPosCode(agvArriveResponseParams.getArrivePos());
        robotNextStepDto.setClientId(agvArriveResponseParams.getSbID());
        SpringUtil.getApplicationContext().publishEvent(robotNextStepDto);
        return AgvCallResult.builder()
                .ret(true).sbID(agvArriveResponseParams.getSbID()).build();
    }

    @PostMapping(value = "/agv/pick")
    @Deprecated
    public AgvCallResult pick(@RequestBody AgvPickParams agvPickParams) {
        //AgvCallResult agvCallResult = agvDao.pick(agvPickParams);
        //log.info("pick: {}", agvCallResult.toString());
        //HttpHeaders headers = new HttpHeaders();
        //headers.setContentType(MediaType.valueOf("application/json;UTF-8"));
        //JSONObject jsonObject = new JSONObject();
        //jsonObject.put("faultPos", "911013");
        //jsonObject.put("src", "911013");
        //jsonObject.put("desc", "911013");
        //jsonObject.put("sbID", "sr_agv1");
        //jsonObject.put("problem", "急停按钮触发");
        //HttpEntity<String> strEntity = new HttpEntity<String>(jsonObject.toString(), headers);
        //String result = restTemplate.postForObject("", strEntity, String.class);
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/agv/store")
    @Deprecated
    public AgvCallResult store(@RequestBody AgvStoreParams agvStoreParams) {
        //AgvCallResult agvCallResult = agvDao.store(agvStoreParams);
        //log.info("store: {}", agvCallResult.toString());
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }


    @PostMapping(value = "/scx/start")
    @Deprecated
    public AgvCallResult start(@RequestBody ScxStatus status) {
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/scx/stop")
    @Deprecated
    public AgvCallResult stop(@RequestBody ScxStatus status) {
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/agv/fault")
    @Deprecated
    public AgvCallResult fault() {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.valueOf("application/json;UTF-8"));
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("faultPos", "911013");
        jsonObject.put("src", "911013");
        jsonObject.put("desc", "911013");
        jsonObject.put("sbID", "sr_agv1");
        jsonObject.put("problem", "急停按钮触发");

        HttpEntity<String> strEntity = new HttpEntity<String>(jsonObject.toString(), headers);
        String result = restTemplate.postForObject("http://276to9673ki4.vicp.fun/api/v1/centreApp/agv/fault/", strEntity, String.class);

        //AgvFaultParams agvFaultParams = new AgvFaultParams();
        //agvFaultParams.setFaultPos("91119");
        //agvFaultParams.setSrc("91119");
        //agvFaultParams.setDesc("91119");
        //agvFaultParams.setSbID("sr_agv1");
        //agvFaultParams.setProblem("急停触发");
        //agvDao.fault(agvFaultParams);

        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/agv/arrive")
    @Deprecated
    public AgvCallResult arrive(@RequestBody AgvArriveResponseParams agvArriveResponseParams) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.valueOf("application/json;UTF-8"));
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sbID", agvArriveResponseParams.getSbID());
        jsonObject.put("fktime", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        jsonObject.put("arrivePos", "91119");
        HttpEntity<String> strEntity = new HttpEntity<String>(jsonObject.toString(), headers);
        String result = restTemplate.postForObject("http://276to9673ki4.vicp.fun/api/v1/centreApp/agv/arrive/",
                strEntity, String.class);
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }


    @PostMapping(value = "/agv/workMode")
    @Deprecated
    public AgvCallResult workMode(@RequestBody AgvWorkModeParams workModeParams) {
        //AgvCallResult agvCallResult = agvDao.workmode(workModeParams);
        //log.info("work: {}", agvCallResult.toString());
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/agv/powerOn")
    @Deprecated
    public AgvCallResult powerOn(@RequestBody PowerRobotParams powerRobotParams) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.valueOf("application/json;UTF-8"));
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("sbID", "sr_agv1");
        jsonObject.put("fktime", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));

        HttpEntity<String> strEntity = new HttpEntity<String>(jsonObject.toString(), headers);
        String result = restTemplate.postForObject("http://276to9673ki4.vicp.fun/api/v1/incomeDecomposeApp/agv/poweron", strEntity, String.class);


        //CallAgvResult agvCallResult = agvDao.powerOn(powerRobotParams);
        //log.info("work: {}", agvCallResult.toString());
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    @PostMapping(value = "/agv/powerOff")
    @Deprecated
    public AgvCallResult powerOff(@RequestBody PowerRobotParams powerRobotParams) {
        //CallAgvResult agvCallResult = agvDao.powerOff(powerRobotParams);
        //log.info("work: {}", agvCallResult.toString());
        return AgvCallResult.builder()
                .ret(true).sbID("sr_agv1").build();
    }

    // 轮对。3.241 3.242 道闸
    // 轮轴 0.241 0.242
    @GetMapping(value = "/agv/currentPos")
    @Operation(summary = "获取AGV当前坐标状态-数字孪生平台使用")
    public R<List<RobotPosDto>> currentPos() {
        // 清除错误、 重定位、 订单清除、 急停、 调度上下线(清除控制权)
        List<RobotPosDto> list = Lists.newArrayList();
        List<RobotEntity> robotEntities = robotService.list();
        Cache<String, RobotClientSummaryDto> posInfoDtoCache = SpringUtil.getBean("robotCache");
        for (RobotEntity robotEntity : robotEntities) {
            RobotClientSummaryDto posInfoDto = posInfoDtoCache.getIfPresent(robotEntity.getRobotSn());
            RobotPosDto robotPosDto = new RobotPosDto();
            if (posInfoDto != null) {
                robotPosDto.setPosX(String.valueOf(posInfoDto.getPosition().getLaserPos().getX()));
                robotPosDto.setPosY(String.valueOf(posInfoDto.getPosition().getLaserPos().getY()));
                robotPosDto.setYaw(String.valueOf(posInfoDto.getPosition().getLaserPos().getYaw()));
                robotPosDto.setAgvName(posInfoDto.getStatus().getName());
                robotPosDto.setAgvSn(posInfoDto.getStatus().getSn());
                robotPosDto.setOnlineState(posInfoDto.getStatus().getOnlineStatusStr());
                robotPosDto.setRunningState(posInfoDto.getStatus().getWorkStatusStr());
                robotPosDto.setAlarmState("");
                try {
                    robotPosDto.setJackIsFull(Optional.ofNullable(posInfoDto.getStatus().getIsFull()).orElse(false));
                } catch (Exception e) {
                    //throw new RuntimeException(e);
                    robotPosDto.setJackIsFull(false);
                }
            } else {
                robotPosDto.setPosX("0");
                robotPosDto.setPosY("0");
                robotPosDto.setYaw("0");
                robotPosDto.setAgvName(robotEntity.getRobotName());
                robotPosDto.setAgvSn(robotEntity.getRobotSn());
                robotPosDto.setOnlineState("离线");
                robotPosDto.setRunningState("空闲");
                robotPosDto.setAlarmState("");
                robotPosDto.setJackIsFull(false);
            }
            list.add(robotPosDto);
        }
        return R.ok(list);
    }


    @GetMapping(value = "/agv/gateStatus")
    @Operation(summary = "道闸状态")
    public R<List<GateStatusDto>> gateStatus() {
        List<GateStatusDto> list = Lists.newArrayList();
        GateStatusDto dto = new GateStatusDto();
        dto.setIp("172.24.3.241");
        dto.setStatus(1);
        list.add(dto);
        dto = new GateStatusDto();
        dto.setIp("172.24.3.242");
        dto.setStatus(0);
        list.add(dto);
        return R.ok(list);
    }

    @PostMapping(value = "/agv/semiAutomatic")
    @Operation(summary = "AGV请求任务-半自动任务模式")
    @SysLog("AGV请求任务-半自动任务模式")
    public R semiAutomatic(@RequestBody SemiAutomaticParams agvCallRequestParams) {
        try {
            log.info("半自动任务模式AGV: {}", JSONObject.toJSONString(agvCallRequestParams));
            String taskName = createTaskName(agvCallRequestParams.getSrc(), agvCallRequestParams.getDes());
            String src = agvCallRequestParams.getSrc();
            String dest = agvCallRequestParams.getDes();
            Integer num = Optional.of(agvCallRequestParams.getSrcnum()).orElse(1);
            Integer srcPlace = agvCallRequestParams.getSrcplace();
            Long maxSn = jobCachesService.getMaxSn(6);
            for (int i = 0; i < num; i++) {
                // 组建任务数据
                List<AgvCallDataDto> newData = agvCallRequestParams.getData();
                RobotJobEntity robotJob = new RobotJobEntity();
                robotJob.setCron("0 * * * * ?");
                robotJob.setName(taskName + ":时间-" + DateUtil.format(new Date(), "yyyyMMddHH:mm:ss"));
                if (agvCallRequestParams.getData() != null) {
                    robotJob.setRemark(JSONObject.toJSONString(agvCallRequestParams.getData()));
                }
                robotJob.setGroupId(6);
                String content = "{\"robotSn\":\"sr_002\",\"id\":null,\"formTitle\":\"运输工单上下文参数配置\",\"formName\":\"tickets_params_agv\",\"fields\":[{\"id\":45,\"name\":\"targetPosCode\",\"label\":\"目标点位编码\",\"type\":\"radio\",\"defaultValue\":null,\"currentValue\":\"LM34\",\"required\":true,\"min\":null,\"max\":null,\"pattern\":null,\"options\":[\"{\\\"label\\\":\\\"LM34\\\",\\\"value\\\":\\\"LM34\\\"}\"]},{\"id\":46,\"name\":\"sourcePosCode\",\"label\":\"起始点位编码\",\"type\":\"radio\",\"defaultValue\":null,\"currentValue\":\"LM23\",\"required\":true,\"min\":null,\"max\":null,\"pattern\":null,\"options\":[\"{\\\"label\\\":\\\"LM23\\\",\\\"value\\\":\\\"LM23\\\"}\"]}]}";
                content = content.replaceAll("LM34", dest).replaceAll("LM23", src);

                FormConfigModalDto jsonArray = JSONObject.parseObject(content, FormConfigModalDto.class);
                List<FormFieldConfigModalDto> formFieldConfigModalDtos = jsonArray.getFields();
                FormFieldConfigModalDto dto = new FormFieldConfigModalDto();
                dto = new FormFieldConfigModalDto();
                dto.setLabel("srcplace");
                dto.setName("srcplace");
                dto.setCurrentValue("" + srcPlace);
                formFieldConfigModalDtos.add(dto);
                dto = new FormFieldConfigModalDto();
                dto.setLabel("srcnum");
                dto.setName("srcnum");
                dto.setCurrentValue("4");
                formFieldConfigModalDtos.add(dto);
                dto = new FormFieldConfigModalDto();
                dto.setLabel("pjData");
                dto.setName("pjData");
                dto.setCurrentValue(JSONObject.toJSONString(newData));
                formFieldConfigModalDtos.add(dto);
                dto = new FormFieldConfigModalDto();
                dto.setLabel("auto");
                dto.setName("auto");
                // 1-自动 0-半自动
                dto.setCurrentValue("0");
                formFieldConfigModalDtos.add(dto);
                jsonArray.setFields(formFieldConfigModalDtos);
                robotJob.setContent(JSONObject.toJSONString(jsonArray));
                // 先查询执行的顺序
                JobCachesEntity jobCachesEntity = new JobCachesEntity();
                // 两边都分配固定的分组id
                jobCachesEntity.setGroupId(6);
                jobCachesEntity.setTaskName(createTaskName(src, dest));
                jobCachesEntity.setJobType(jobCachesEntity.getTaskName());
                // 执行的顺序
                jobCachesEntity.setSn((int) (i + 1 + maxSn));
                long snowflakeId = System.currentTimeMillis();
                jobCachesEntity.setTaskNo(String.valueOf(snowflakeId));
                jobCachesEntity.setTaskContent(robotJob.getContent());
                jobCachesService.save(jobCachesEntity);
                SpringUtil.getApplicationContext().publishEvent(new SubscriptTopicDto("jobCaches"));
            }
            return R.ok("任务已经添加，请等待执行");
        } catch (Exception e) {
            log.info("数据异常无法解析:" + e.getMessage());
            return R.failed("呼叫失败");
        }
    }


    public String getClientIpAddress(HttpServletRequest request) {
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        return ipAddress;
    }


    @GetMapping(value = "/agv/storeBuffersStatus")
    @Operation(summary = "缓存架状态")
    @SysLog("获取缓存架状态")
    public R<List<StoreStatusDto>> storeBuffersStatus() {
        List<StoreStatusDto> list = Lists.newArrayList();
        LambdaQueryWrapper<GoodsBuffersEntity> wrapper = Wrappers.lambdaQuery();
        List<GoodsBuffersEntity> goodsBuffersEntities = goodsBuffersService.list(
                wrapper.orderByAsc(GoodsBuffersEntity::getName)
                        .orderByAsc(GoodsBuffersEntity::getSn)
        );
        for (GoodsBuffersEntity goodsBuffersEntity : goodsBuffersEntities) {
            StoreStatusDto storeStatusDto = new StoreStatusDto();
            BeanUtil.copyProperties(goodsBuffersEntity, storeStatusDto);
            storeStatusDto.setStoreStatusStr(storeStatusDto.getStoreStatus() == 1 ? "未存放" : "已存放");
            list.add(storeStatusDto);
        }
        return R.ok(list);
    }

    @Operation(summary = "玻璃房通知AGV可以走")
    @PostMapping(value = "/agv/continue")
    public AgvCallResult agvContinue(@RequestBody AgvContinueParams params) {

        TicketsEntity ticketsEntity = ticketsService.getOne(Wrappers.<TicketsEntity>lambdaQuery().
                eq(TicketsEntity::getTaskNo, params.getTaskCode()).last(" limit 1"));

        String robotSn = ticketsEntity.getAllotRobotSn();
        if (StringUtils.hasText(params.getSrc())) {
            RobotNextStepDto robotNextStepDto = new RobotNextStepDto();
            robotNextStepDto.setTaskCode(params.getTaskCode());
            robotNextStepDto.setPosCode(params.getSrc());//"911022"或"911021"
            robotNextStepDto.setClientId(robotSn);
            SpringUtil.getApplicationContext().publishEvent(robotNextStepDto);
        }

        return AgvCallResult.builder().ret(true).message("").build();
    }


    @Operation(summary = "查询最佳的放位置")
    @PostMapping(value = "/agv/goodsBufferFindPlacePut")
    public R goodsBufferFindPlacePut(@RequestBody FindPlacePutDto dto
    ) {
        return R.ok(agvStoreService.findBestPlace(
                dto.getSrc(), "911017", Integer.parseInt(dto.getNum()), dto.getZx(), Integer.parseInt(dto.getSrcPlace()), dto.getStoreData()));
    }


    @Operation(summary = "缓存架更新数据--AGV放/存完成通知")
    @PostMapping(value = "/agv/updateRealStore")
    public R updateRealStore(String bindPosName, String num) {
        GoodsBuffersEntity entity = goodsBuffersService.getOne(Wrappers.<GoodsBuffersEntity>query()
                .lambda()
                .eq(GoodsBuffersEntity::getBindPosName, bindPosName)
                .last("limit 1")
        );

        List<GoodsBuffersEntity> updateList = goodsBuffersService.list(Wrappers.<GoodsBuffersEntity>query()
                .lambda()
                .eq(GoodsBuffersEntity::getGroupId, entity.getGroupId())
                .ge(GoodsBuffersEntity::getSn, entity.getSn())
                .last("limit " + Integer.parseInt(num))
        );

        updateList.forEach(item -> {
            int storeStatus = item.getStoreStatus().intValue();
            if (storeStatus == 2 || storeStatus == 3) {
                item.setLastStoreStatus(item.getStoreStatus());
                if (storeStatus == 2) {// 预存放
                    item.setStoreStatus(0);//有存放
                } else if (storeStatus == 3) {// 预拿取
                    item.setRealStoreName("");
                    item.setStoreName("");
                    item.setStoreData("");
                    item.setStoreStatus(1);//  无存放
                }
                goodsBuffersService.updateById(item);
            }
        });
        return R.ok();
    }


    @GetMapping(value = "/agv/getStoreBuffers")
    @Operation(summary = "获取缓存架状态-new")
    @SysLog("获取缓存架状态-new")
    public R<List<StoreBuffersDto>> getStoreBuffers() {
        List<StoreBuffersDto> list = Lists.newArrayList();
        LambdaQueryWrapper<GoodsBuffersEntity> wrapper = Wrappers.lambdaQuery();
        List<GoodsBuffersEntity> goodsBuffersEntities = goodsBuffersService.list(
                wrapper.orderByAsc(GoodsBuffersEntity::getName)
                        .orderByAsc(GoodsBuffersEntity::getSn)
        );
        for (GoodsBuffersEntity entity : goodsBuffersEntities) {
            StoreBuffersDto dto = new StoreBuffersDto();
            dto.setName(entity.getName());
            Function<String, Integer> convertType = (name) -> {
                return switch (name) {
                    case "旧轴" -> 1;
                    case "返修" -> 2;
                    case "报废" -> 3;
                    case "新轴" -> 4;
                    default -> -1;
                };
            };
            dto.setType(convertType.apply(String.valueOf(entity.getRealStoreName())));
            dto.setStatus(entity.getStoreStatus());
            String storeData = entity.getStoreData();
            if (StringUtils.hasText(storeData)) {
                JSONObject jsonObject = JSONObject.parseObject(entity.getStoreData());
                dto.setPj_id(jsonObject.getString("pj_id"));
                dto.setZx(jsonObject.getString("zx"));
                dto.setZh(jsonObject.getString("zh"));
            }
            list.add(dto);
        }
        return R.ok(list);
    }


    @PostMapping(value = "/agv/modifyStoreBuffers")
    @Operation(summary = "修改缓存架状态-new")
    @SysLog("修改缓存架状态-new")
    public R<CommonRetDto> modifyStoreBuffers(@RequestBody StoreBuffersDto params) {
        Function<Integer, String> convertType = (type) -> {
            return switch (type) {
                case 1 -> "旧轴";
                case 2 -> "返修";
                case 3 -> "报废";
                case 4 -> "新轴";
                default -> "未知";
            };
        };
        GoodsBuffersEntity entity = goodsBuffersService.getOne(
                Wrappers.<GoodsBuffersEntity>lambdaQuery()
                        .eq(GoodsBuffersEntity::getName, params.getName())
                        .last("limit 1")
        );
        if (entity != null) {
            entity.setRealStoreName(convertType.apply(params.getType()));
            entity.setStoreStatus(params.getStatus());
            String storeData = entity.getStoreData();
            JSONObject storeDataJson = StringUtils.hasText(storeData) ?
                    JSONObject.parseObject(entity.getStoreData()) : new JSONObject();
            storeDataJson.put("pos", params.getName());
            storeDataJson.put("pj_id", params.getPj_id());
            storeDataJson.put("zx", params.getZx());
            storeDataJson.put("zh", params.getZh());
            entity.setStoreData(storeDataJson.toJSONString());
            goodsBuffersService.updateById(entity);
        }
        return R.ok(CommonRetDto.builder()
                .ret(true)
                .message("")
                .build());
    }

    @PostMapping(value = "/agv/resetStoreBuffers")
    @Operation(summary = "重置缓存架状态-new")
    @SysLog("重置缓存架状态-new")
    public R<CommonRetDto> resetStoreBuffers(@RequestBody ResetStoreBuffersDto params) {
        Assert.notEmpty(params.getNames(), "缓存架名称不能为空");
        goodsBuffersService.update(Wrappers.<GoodsBuffersEntity>lambdaUpdate()
                .in(GoodsBuffersEntity::getName, params.getNames())
                .set(GoodsBuffersEntity::getStoreName, null)
                .set(GoodsBuffersEntity::getRealStoreName, null)
                .set(GoodsBuffersEntity::getStoreStatus, 1)
                .set(GoodsBuffersEntity::getStoreData, null)
                .set(GoodsBuffersEntity::getStoreTime, null)
                .set(GoodsBuffersEntity::getLastStoreStatus, null)
        );
        return R.ok(CommonRetDto.builder()
                .ret(true)
                .message("")
                .build());
    }

    @Operation(summary = "缓存架位置存、取二次校验")
    @PostMapping(value = "/agv/checkPickBlock")
    public R checkPickBlock(String bindPosName, String num, String operateType) {
        GoodsBuffersEntity current = goodsBuffersService.getOne(Wrappers.<GoodsBuffersEntity>query()
                .lambda()
                .eq(GoodsBuffersEntity::getBindPosName, bindPosName)
                .last("limit 1")
        );
        List<GoodsBuffersEntity> goodsBuffersEntities = goodsBuffersService
                .list(Wrappers.<GoodsBuffersEntity>lambdaQuery()
                        .eq(GoodsBuffersEntity::getGroupId, current.getGroupId())
                        .ge(GoodsBuffersEntity::getSn, current.getSn())
                        .orderByAsc(GoodsBuffersEntity::getSn)
                        .last("limit " + num)
                );
        if (!goodsBuffersEntities.isEmpty()) {
            GoodsBuffersEntity outside = goodsBuffersEntities.stream()
                    .sorted(Comparator.comparing(GoodsBuffersEntity::getSn).reversed())
                    .findFirst().orElse(null);//需要存或取的最后一根车轴
            GoodsBuffersEntity exits = goodsBuffersService.getOne(Wrappers.<GoodsBuffersEntity>lambdaQuery()
                    .eq(GoodsBuffersEntity::getGroupId, outside.getGroupId())
                    .eq(GoodsBuffersEntity::getStoreStatus, 0)
                    .gt(GoodsBuffersEntity::getSn, outside.getSn())
                    .last("limit 1"));
            if (exits != null) {
                log.error("有车轴挡住无法{}！ bindPosName:{},num:{}",
                        "take".equals(operateType) ? "取出!" : "放入!", bindPosName, num);
                return R.failed("有车轴挡住无法" + ("take".equals(operateType) ? "取出!" : "放入!"));
            }
        }
        return R.ok();
    }

    @PostMapping(value = "/agv/run/doing")
    @Operation(summary = "告知正在执行的机器人")
    @SysLog("告知正在执行的机器人")
    public R<CommonRetDto> runDoing(@RequestBody RunDoingDto dto) {
        log.info("\n告知正在执行的机器人=====>{}", JSONUtil.toJsonStr(dto));
        return R.ok(CommonRetDto.builder()
                .ret(true)
                .message("")
                .build());
    }

    @GetMapping(value = "/agv/resetFakeStoreBuffers")
    @Operation(summary = "重置预存、预取接口")
    public CommonRetDto resetGoodsBuffers() {
        //重置预存放
        Boolean b1 = goodsBuffersService.update(Wrappers.<GoodsBuffersEntity>lambdaUpdate()
                .eq(GoodsBuffersEntity::getStoreStatus, 2)
                .set(GoodsBuffersEntity::getStoreName, "")
                .set(GoodsBuffersEntity::getRealStoreName, "")
                .set(GoodsBuffersEntity::getStoreData, "")
                .set(GoodsBuffersEntity::getStoreStatus, 1)
        );
        //重置预拿取
        Boolean b2 = goodsBuffersService.update(Wrappers.<GoodsBuffersEntity>lambdaUpdate()
                .eq(GoodsBuffersEntity::getStoreStatus, 3)
                .set(GoodsBuffersEntity::getStoreStatus, 0)
        );
        return CommonRetDto.builder()
                .ret(true).message("").build();
    }
}
